“La energía es el hardware del universo. El software es la información” (Ervin Laszlo)
La Unión de Hardware y Software
Es lógico plantearse la posibilidad de la unificación de hardware y software y sobre la naturaleza de esa unión.
Decir hardware, como su nombre indica, es decir rigidez. Y decir software es decir flexibilidad. De hecho el software se inventó para flexibilizar un diseño hardware. En los primeros tiempos de los ordenadores, cambiar un programa suponía cambiar las conexiones de la circuitería de la máquina. El gran salto evolutivo fue la idea de almacenar el código del programa en memoria, junto con los datos. Esto tenía varias ventajas:
La posibilidad de realizar diferentes procesos con el mismo hardware, sin necesidad de cambiar las conexiones de los circuitos.
La posibilidad de modificar el código del programa durante la ejecución.
La posibilidad de crear meta-programas, es decir, programas que generasen programas.
El hardware ha evolucionado para dar soporte a ciertos aspectos de la programación, como la modularización, la programación estructurada, el multiproceso, la memoria virtual, etc.
Es evidente que la dependencia del software respecto al hardware debería ser la mínima posible, pues a menor dependencia del hardware, mayor es la flexibilidad. Además, siempre se ha planteado el tema del diseño del procesador ideal y del lenguaje ideal, así como de la forma de interacción entre ambos.
Para hacer flexible un procesador se ha utilizado microcódigo programable, un código basado en instrucciones de muy bajo nivel (microinstrucciones). Esta tecnología ya ha desaparecido por completo porque existen herramientas avanzadas para diseñar complejas unidades de control que tienen un rendimiento significativamente mayor que cualquier unidad microprogramada.
La dualidad código fuente - código de máquina
En los sistemas convencionales, el hardware del ordenador no puede ejecutar directamente el programa fuente del usuario. Se necesita un compilador o intérprete que transforme el programa fuente en código objeto, el código binario o código de máquina ejecutable por el hardware. Los problemas de este proceso son: 1) su coste computacional (tiempo de proceso y recursos de máquina); 2) ineficiencia, porque el código objeto no suele ser óptimo o es poco adecuado; 3) la generación de errores de compilación, porque los compiladores son difíciles de desarrollar; 4) los mensajes de error durante la ejecución suelen ser crípticos porque se expresan en el lenguaje de bajo nivel de la máquina, y no en el lenguaje de alto nivel del usuario.
La compatibilidad binaria ha sido un problema desde el comienzo de los lenguajes de programación, pues el código binario liga el software a un hardware particular. Para reducir o eliminar este problema, se han propuesto básicamente dos soluciones:
El desarrollo de “máquinas virtuales”, máquinas intermediarias entre el hardware real y el programa a ejecutar.
El desarrollo de procesadores orientados a un lenguaje determinado, con el fin de optimizar el rendimiento al haber un interfaz homogéneo entre software y hardware.
Las máquinas virtuales
Las máquinas virtuales utilizan un juego de instrucciones abstracto, no perteneciente a ninguna máquina específica. Un compilador genera un código abstracto en ese lenguaje abstracto y la máquina virtual interpreta y ejecuta ese código abstracto. La ventaja del código abstracto es su portabilidad: el mismo código puede ser ejecutado por diferentes plataformas. El código abstracto es muy compacto, por lo que el rendimiento es mayor. Para que ese código objeto sea posible es necesario desarrollar una máquina virtual en cada plataforma hardware. La máquina virtual se puede usar como un sistema operativo completo o ejecutarse bajo otro sistema operativo.
Hay dos formatos de código abstracto para máquina virtual: bytecode y p-code.
El bytecode es un archivo binario que contiene un programa ejecutable, similar a un módulo objeto. Su nombre proviene del hecho de que cada código de operación ocupa un byte. Los ficheros bytecode son interpretados por una máquina virtual. Una máquina virtual es un intérprete de bytecode. Los compiladores “Just in Time” traducen bytecode en código de máquina ejecutando una instrucción cada vez.
El p-code (código portable o psedo-código) es similar al bytecode, pero los códigos de operación pueden constar de más de un byte.
Java y Smalltalk utilizan bytecode. UCSD Pascal utiliza p-code.
Una máquina virtual Java es una máquina virtual capaz de interpretar y ejecutar instrucciones expresadas en el bytecode Java, el código binario generado por el compilador del lenguaje Java. La máquina virtual Java actúa como intermediario entre el hardware y el bytecode. Existen diferentes máquinas virtuales Java para diferentes arquitecturas hardware.
La máquina virtual Smalltalk −el primer lenguaje orientado a objetos− es una máquina virtual que permite que una imagen Smalltalk (fichero bytecode) se ejecute bajo una plataforma. El nombre de “imagen” hace referencia a que una imagen de la memoria interna de la máquina se graba en memoria externa (disco). Los objetos Smalltalk “sobreviven” tras finalizar la ejecución.
UCSD Pascal es un lenguaje de programación que funciona sobre el UCSD p-System, un sistema operativo independiente de la máquina, que se calificó en su día como “sistema operativo universal”. UCSD Pascal tuvo una gran influencia en la máquina virtual Smalltalk y en el diseño de la máquina virtual Java.
La máquina virtual de UCSD p-System se denomina “p-machine” (o psedo-machine), con su propio juego de instrucciones llamado p-code (o pseudo-code). Cada plataforma hardware solo necesita un intérprete de p-code para portar el sistema entero p-System y todas sus herramientas.
Procesadores orientados a un lenguaje de programación
Las ventajas de la unificación entre hardware y software mediante un procesador orientado a un lenguaje de programación son:
Eliminación del intérprete, compilador o máquina virtual. Ya no sería necesario un sistema intermediario. Habría comunicación directa, homogénea y coherente entre hardware y software. La máquina virtual sería igual a la real.
Eliminación del gap semántico entre ambas tecnologías al funcionar con un único paradigma.
Eliminación de la distinción entre lenguaje fuente y lenguaje objeto. Serían el mismo.
Mejoría del rendimiento de la máquina.
Portabilidad perfecta de las aplicaciones entre un sistema y otro.
La Evolución de los Procesadores
Los procesadores han ido evolucionando a lo largo de la historia de la informática. Desde los procesadores complejos, rígidos y restrictivos, pasando por los simplificados y llegando hasta los minimalistas, siempre en el sentido de una mayor simplicidad, flexibilidad y eficiencia. La evolución ha sido la siguiente:
CISC (Complex Instruction Set Computer)
Fueron los primeros procesadores. Su nombre se lo asignaron los partidarios de procesadores más reducidos y simples (los RISC). Los procesadores CISC implementan un conjunto muy amplio de instrucciones complejas, con operandos situados en la memoria o en registros internos de la máquina.
En los primeros tiempos de los ordenadores, la tecnología de compilación no existía. La programación se tenía que realizar en lenguaje ensamblador. Para facilitar la tarea del programador, se crearon instrucciones complejas, que eran como las funciones de los lenguajes de programación de alto nivel. Esto condujo a procesadores cada vez más complejos y sofisticados. Cuando aparecieron los compiladores, en los que una instrucción de alto nivel se podía implementar en varias instrucciones elementales, ya no era necesario hace esto, por lo que se podía simplificar el hardware.
RISC (Reduced Instruction Set Computer)
Los procesadores RISC se crearon para mejorar el rendimiento de los procesadores CISC. Para ello se simplificó el diseño:
Menor número de instrucciones y más simples.
Instrucciones de tamaño fijo y con un número reducido de formatos.
No necesita microcódigo.
Las operaciones son suficientemente simples para que se ejecuten en un solo ciclo, por lo que son más eficientes.
Se reducen los acceso a memoria mediante la utilización de muchos registros de propósito general (pues las operaciones con ellos son más rápidas), con dos instrucciones especializadas para la transferencia entre registro y memoria (load y store), y con gran memoria caché.
El direccionamiento es homogéneo y simple en todas las instrucciones, tanto a registros como a memoria.
Se posibilita el paralelismo, tanto en la ejecución de instrucciones como en los programas.
No se soportan tipos de datos. Por lo tanto, no se necesitan instrucciones especializadas para cada tipo de datos.
Hoy día, la gran mayoría de los procesadores son RISC.
MISC (Minimal Instruction Set Computer)
Es un procesador con un número muy pequeño de instrucciones. Está basado en una pila, en lugar de registros para reducir el tamaño de los operandos, pues todas las instrucciones toman los operandos de lo más alto de la pila. Normalmente, un MISC consta 32 instrucciones como máximo.
Las ventajas son que la unidad de decodificación (interpretación del código) es más simple y rápida, y las instrucciones son más eficientes.
OISC (One Instruction Set Computer)
Es una máquina teórica (o abstracta) que tiene solo una instrucción pero con mayor número de operandos. Las instrucción elegida suele ser del tipo “Restar y bifurcar si condición”. La condición suele ser “menor o igual que cero”, “negativo” o “distinto de cero”. Si no se cumple la condición, la ejecución pasa a la instrucción siguiente. Ejemplos:
Subleca, b, c (restar el contenido de la dirección a del contenido de la dirección b y bifurcar a la dirección c si b es menor o igual que cero; c es opcional)
Sbnza, b, c, d (resta el contenido de la dirección a del contenido de la dirección b y almacena el resultado en c, y si el resultado en c no es cero transfiere el control a la dirección d).
Características:
Está orientado a cálculos aritméticos.
Supone que en cada posición de memoria se almacena un número entero.
Es un computador universal (Turing completo), es decir, puede realizar los mismos procesos que una máquina de Turing, como un procesador CISC, RISC o MISC.
Las propias instrucciones se almacenan en memoria como una secuencia de enteros, pues no se necesita el código de operación, al haber solo una.
Todas las demás instrucciones son derivadas (sintetizadas) de la única instrucción existente.
Ejemplos de instrucciones derivadas de Sbnz:
Bifurcar incondicionalmente a d.
Jumpd ⇒ Sbnzz, z, z, d (z es una posición que contiene cero)
Sumar a y b y almacenar el resultado en c.
Adda, b ⇒ Sbnzz, a, d, zSbnzd, b, c, z
Mover (copiar) a a b.
Mova, b ⇒ Sbnzz, a, b
Bifurcar a d si c es distinto de cero.
Beqd, c ⇒ Sbnzz, a, c, d
VLIW (Very Long Instruction Word)
Es una arquitectura de procesador diseñada para aprovecharse del paralelismo a nivel de instrucción. Es tarea del compilador el controlar la planificación de las instrucciones y la protección de los datos.
NISC (No Instruction Set Computer)
Los procesadores NISC son los sucesores de los VLIW. Es una arquitectura de procesador altamente eficiente que permite a un compilador tener un control de bajo nivel de los recursos hardware. Tiene control horizontal (la secuencia de las instrucciones) y vertical (el paralelismo de las instrucciones). La arquitectura es mucho más simple (pues no se necesita decodificar las instrucciones), más flexible y eficiente.
ZISC (Zero Instruction Set Computer)
Es un procesador sin instrucciones. Incorpora en su lugar una tecnología orientada a reconocimiento de patrones (pattern matching), por lo que no tienen instrucciones en el sentido clásico. Está basada en las ideas de las redes neuronales artificiales y en el proceso masivamente paralelo.
La primera generación de un chip ZISC contenía 36 células independientes, en donde cada célula se puede considerar como una neurona artificial o un procesadores paralelo. Cada célula puede comparar un vector de entrada de hasta 64 bytes con otro vector en memoria. Si el vector de entrada coincide con el vector de la memoria, las células emiten una señal de salida que contiene el número de la célula que ha reconocido el vector o una señal de no reconocimiento.
El poder de un ZISC reside en la escalabilidad. Una red ZISC se puede expandir añadiendo más procesadores ZISC sin que decrezca su velocidad de reconocimiento. Y no hay limitación teórica respecto al número de células que puede haber en una red.
Los chips ZISC se pueden utilizar también para hacer reconocimientos difusos (fuzzy). En lugar de un reconocimiento exacto, se puede obtener el reconocimiento más próximo. Las células que están por encima de un cierto umbral se disparan simultáneamente y el controlador ZISC localiza la célula que devuelve el valor más próximo.
Procesadores Orientados a Lenguajes de Programación
Existen antecedentes de implementación sobre hardware de un lenguaje de programación. Los más destacados son los siguientes:
Rekursiv
Rekursiv fue un microprocesador orientado a objetos desarrollado diseñado por David M. Harland (de la Universidad de Glasgow) a mediados de los años 1980’s para la empresa Linn Smart (Glasgow) para controlar su sistema de fabricación de equipos Hi-Fi. La motivación principal fue la mejora del rendimiento de Lingo, un lenguaje orientado a objetos (derivado de Smalltalk y Algol), también desarrollado por la misma empresa, que no era satisfactorio con el hardware de su época (Digital Vax). En aquella época no era muy conocido aún el paradigma objetual, por lo que tuvo bastante mérito embarcarse en esa aventura.
El conjunto de instrucciones del procesador soportaba recursión (de ahí su nombre).
El juego de instrucciones del procesador era programable mediante microcódigo para mejorar aún más el rendimiento de la aplicaciones.
El procesador no era un solo microprocesador, sino una placa madre con 4 chips funcionando en paralelo: Numerik (unidad aritmético-lógica), Logik (secuenciador de instrucciones), Objekt (gestor de objetos) y Klock (reloj). La CPU constaba de Numerik y Logik.
Utilizaba memoria virtual como almacén de objetos persistente.
A cada objeto se le asignaba, al crearlo, un identificador. En la memoria del objeto se almacenaban datos e instrucciones. Los datos podían ser identificadores de otros objetos. Dentro de la identificador del objeto se incluia la identificación del procesador que había creado dicho objeto. También existían objetos compactos (como números, cadenas de caracteres, etc.) que incluían su propio identificador.
El procesador realizaba automáticamente la “recogida de basura” (eliminación de la memoria de los objetos no utilizados).
No implementaba seguridad.
No incluía todas las funcionalidades de la orientación a objetos. Por ejemplo, no soportaba herencia.
Ejecutaba Lingo suficientemente rápido, con un encaje perfecto entre lenguaje y CPU. Podía también ejecutar lenguajes convencionales como Smalltalk y C.
El procesador era realmente innovador y fue sorprendentemente fácil de implementar. El proyecto finalmente se abandonó porque Linn no tenía recursos suficientes para proseguir con esa prometedora arquitectura. Pero demostró la viabilidad de construcción de procesadores de alto nivel de abstracción de hardware para acercarlos a los lenguajes orientados a objetos.
Procesadores Forth
Forth es un lenguaje simple y eficiente, desarrollado por Chuck Moore y Elisabeth Rather entre los años 1965 y 1970, para aplicaciones astronómicas en tiempo real, pero que evolucionó hasta convertirse en un lenguaje de programación general y en un entorno de programación. Actualmente se usa principalmente para programar sistemas empotrados (pequeños dispositivos computerizados) y cargadores (boot loaders).
Es procedimental, imperativo, estructurado y recursivo. No tiene tipos de datos.
Manipula los datos mediante una pila (stack), que son listas LIFO (Last In First Out), en lugar de los registros tradicionales. En la pila se almacenan las expresiones en notación polaca inversa (notación postfija). Por ejemplo, (a+b)×c se almacena en la pila como ab+c×.
Permite la ejecución interactiva de comandos y procedimientos.
Es modular y extensible. Usa un diccionario de palabras, que puede ampliarse. El interprete forma parte del diccionario de palabras.
Los programas se ejecutan en una máquina virtual simple que hace a los programas independientes de la CPU (solo se necesita desarrollar esta máquina virtual en cada tipo de máquina). Existen sistemas Forth que funcionan bajo sistemas operativos conocidos (Windows, Linux, etc.) mediante una máquina virtual que es fácil de desarrollar.
No requiere sistema operativo ni sistema de archivos. Es multitarea.
No tiene una gramática explícita diferenciada. Todo tiene la misma estructura.
Ls procesadores Forth usan Forth como lenguaje de máquina. Los procesadores de pila (stack computers) están inspirados en el lenguaje Forth. Las ventajas de estos procesadores son:
Son menos complejos que procesadores CISC y RISC.
Programas más compactos.
El rendimiento es mayor.
En la pila se almacenan las direcciones de retorno en la llamada a subrutinas y el paso de parámetros a subrutinas.
Hay asignación dinámica de memoria
Uniformidad de interfaz entre los niveles de hardware y software.
Soporta diferentes lenguajes.
Los compiladores son más fáciles de desarrollar.
Permite gran variedad de aplicaciones, especialmente los sistemas empotrados de control en tiempo real.
Los procesadores Forth y de pila han tenido menor difusión que las máquinas tradicionales de registros.
Procesadores Lisp
Un procesador Lisp (o Lisp machine) es un procesador de propósito general y de alto nivel de abstracción diseñado para ejecutar eficientemente código Lisp. Lisp es un lenguaje de inteligencia artificial de tipo funcional y uno de los más utilizados por su extraordinaria flexibilidad. La máquina Lisp estaba orientada al desarrollo y ejecución de aplicaciones de inteligencia artificial.
El sistema operativo estaba escrito en Lisp. Al ser monousuario era relativamente simple.
Usaba memoria virtual y un recogedor de basura (garbage collector): la eliminación de la memoria de los objetos Lisp no accesibles.
En Lisp, código y datos tienen la misma estructura (la lista) y ambos se almacenan en memoria. Las variables son tipadas en tiempo de ejecución (tipos dinámicos).
Las máquinas Lisp fueron las primeras estaciones de trabajo orientadas a un solo usuario. Muchas tecnologías hoy comunes, como los interfaces gráficos de usuario y el uso del ratón, fueron desarrolladas originalmente en máquinas Lisp.
Con la aparición de los ordenadores personales, las máquinas Lisp prácticamente desaparecieron. Solo han sobrevivido dos: Symbolics y Macrosyma.
Procesadores Java
La empresa Sun introdujo Java Virtual Machine (JVM), una capa software sobre una plataforma hardware para el desarrollo y la ejecución de programas Java.
Ha habido varios intentos de construir un procesador que interpretara directamente el bytecode Java como su lenguaje de máquina, pero todos los intentos en este sentido fueron infructuosos. Quizás el fracaso se debió a la complejidad de Java. Un procesador debe ser lo más simple posible.
La Unión Hardware-Software en MENTAL
Con MENTAL se unen todas las dualidades a nivel conceptual. La última dualidad a superar es el tema implementador: unir hardware y software. Que hardware y software hablen el mismo lenguaje: que el conjunto de primitivas semánticas universales sea también el juego de instrucciones del hardware.
De la misma forma que con las primitivas semánticas universales hemos alcanzado las raíces conceptuales de la programación, debemos reflejar esa misma esencia en el hardware para conseguir la máxima homogeneidad, coherencia y eficiencia.
Sería el hardware “ideal”, como MENTAL es el lenguaje ideal. El “juego de instrucciones” de un ordenador (o máquina abstracta) universal (o de propósito general), una máquina más general que la máquina de Turing, pues es operativa y descriptiva.
Si esto fuera posible tendríamos muchas ventajas:
Código abstracto.
No se necesitaría bytecode ni p-code. El código aabstracto sería el de MENTAL. Código fuente y código objeto serían el mismo.
Ejecución directa.
No se necesitaría intérprete del lenguaje, ni compilador ni máquina virtual. Si el procesador no fuera de tipo MENTAL, entonces para ejecutar código MENTAL en otra plataforma habría que desarrollar un intérprete, compilador o máquina virtual.
Mente artificial.
Supondría la máxima inteligencia artificial posible. Se unificaría máquina y mente mediante los 12 grados de libertad que representan las primitivas semánticas universales o arquetipos primarios. Nos aproximaríamos al ideal de la mente artificial, aunque solo en modo de simulación Usamos aquí el término “mente artificial”, más que el término al uso (inteligencia artificial), pues la inteligencia es solo un aspecto de la mente, aunque es el más importante.
Procesador de tipo semántico.
El procesador sería de tipo semántico. Se denomina “proceso semántico” o “análisis semántico” (semantic parser) a la extracción, representación y utilización de semántica (conocimiento o significado) a partir de un texto escrito en lenguaje natural o en un lenguaje artificial (normalmente el código fuente de un lenguaje informático).
El procesador no sería realmente semántico porque la máquina “no sabe” lo que está haciendo (a nivel semántico), pero sería capaz de operar a nivel sintáctico-operativo.
El procesador tendría una triple dimensión: computacional (procesador de la máxima abstracción posible), arquetipal (porque las primitivas son arquetipos de la conciencia) y filosófica (ordenador filosofal, porque las primitivas son también categorías filosóficas).
Universalidad.
Se podrían ejecutar todos los lenguajes de programación que no tengan referencias implementadoras, pues todos derivan de MENTAL, que es el lenguaje madre. También sería el fundamento de todo sistema operativo.
Energía e información.
Si asociamos el hardware con lo fijo y el software con lo flexible, podemos considerar que lo fijo está constituido por las primitivas semánticas universales y que podemos asociar con la energía. Y lo flexible son las manifestaciones de las primitivas, que podemos asociar con la información.
Adenda
MENTAL vs. Forth
Existen numerosas coincidencias entre Forth y MENTAL en los siguientes aspectos:
Son lenguajes simples y los desarrollos se simplifican.
El código es compacto.
No tienen tipos de datos.
Son extensibles.
Permite la ejecución interactiva de comandos y procedimientos.
No requieren un sistema de archivos.
Son multitarea.
No tienen una gramática explícita.
Bibliografía
Álvarez Gutiérrez, Darío. Sistemas integrales orientados a objetos. Disponible en Internet.
Buttazo, G. Can a Machine Ever Become Self-Aware? Artificial Humans. Goethe Institute, Los Angeles, 2000, pp. 45-49.
Dettmer, R. The Rekursiv computer. IEEE Review, vol. 34, issue 1, Jan. 1988, pp. 17-19.
Pountain, Dick. Rekursiv: An Object-Oriented CPU. Byte, Nov. 1988.
Gilreath, William F.; Laplante, Philip A. Computer Architecture. A Minimalist Perspective. Springer, 2003.
Harland, David M.; Gunn, Hammish I.; Pringle, Ian A.; Beloff, Bruno. The Rekursiv. An Arghitecture for Artificial Intelligence. Proceedings of AI / Europa Conference, Wiesbaden, Alemania, September 1986.
Harland, David M.; Beloff, Bruno. Microcoding an Object-Oriented Instruction Set. ACM SIGARCH Computer Architecture News (Association for Computing Machinery) 14 (5): 3, December 1986.
Harland, David M.; Beloff, Bruno. Objekt: A Persistent Object Store with an Integrated Garbage Collector. ACM SIGPLAN Notices (Association for Computing Machinery) 22 (4): 70, April 1987.
Harland, David M. Rekursiv: Object-Oriented Computer Architecture (Ellis Horwood Series in Computers and Their Applications). Ellis Horwood Ltd, August 1986.
Koopman, Philip J. (Jr.). Stack Computers. The New Wave. 1989. Disponible en Internet. (Menciona Rekursiv en el Apéndice A).
Liebman, Sheldon. From CISC to RISC to ZISC. Advanced Imaging, Sept. 1998. Disponible en Internet.
The CPU Shack: Weird and Innovative Chips. Internet.
Williams, Al. The One Instruction Wander. Dr. Dobb´s, Nov. 16, 2009. Disponible en Internet.